package com.cbarmstrong.Finance;

import java.util.Vector;

import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.TimeSeriesDataItem;
import org.jfree.data.xy.OHLCDataItem;
import org.jfree.data.xy.XYDataset;

public class Indicators {
	
	@SuppressWarnings("unchecked")
	public static PlainIndicator EMASet(PlainIndicator indicator, int length){
		//Data.getLogger().fine("EMA: Entering EMA, input: "+indicator.getValues().size());
		double alpha=2.0/((double) length+1), ema=0;
		Vector<Double> vEma=new Vector<Double>();
		Vector<Double> series=(Vector<Double>) indicator.getValues().clone();
		while(series.firstElement()==null){
			series.removeElementAt(0);
			vEma.add(null);
			//Data.getLogger().fine("Added null");
		}
		for(int i=0;i<length;i++){
			ema+=series.get(i);
			vEma.add(null);
			//Data.getLogger().fine(i+") Added null");
		}
		ema=ema/length;
		for(int i=length;i<length*2;i++){
			ema=series.get(i)*alpha+ema*(1-alpha);
			vEma.add(null);
			//Data.getLogger().fine(i+") Added null");
		}
		for(int i=length*2;i<series.size();i++){
			ema=series.get(i)*alpha+ema*(1-alpha);
			vEma.add(ema);
			//Data.getLogger().fine(i+") Added "+vEma.lastElement());
		}
		//Data.getLogger().fine("EMA: Returning EMA, size: "+vEma.size());
		return new PlainIndicator(vEma);
	}
	
	public static PlainIndicator SMAEnvelope(PlainIndicator inSeries, int percent){
		Vector<Double> smae=new Vector<Double>();
		for(int i=0;i<inSeries.getValues().size();i++){
			smae.add((Double) inSeries.getValues().get(i)*(1+percent));
		}
		return new PlainIndicator(smae);
	}
	
	@SuppressWarnings("unchecked")
	public static PlainIndicator SMASet(PlainIndicator inSeries, int length){
		double sma=0;
		Vector<Double> series=(Vector<Double>) inSeries.getValues().clone(), vSma=new Vector<Double>();
		while(series.firstElement()==null){
			series.removeElementAt(0);
			vSma.add(null);
		}
		for(int i=0;i<length;i++){
			sma+=series.get(i);
			vSma.add(null);
		}
		sma=sma/length;
		for(int i=length;i<series.size();i++){
			sma=sma-(series.get(i-length)-series.get(i))/length;
			vSma.add(sma);
		}
		return new PlainIndicator(vSma);
	}
	
	public static PlainIndicator WilliamsSet(Vector<OHLCDataItem> series, int periods){
		Vector<Double> williams=new Vector<Double>();
		if(series==null){ Data.getLogger().warning("Williams attempted with non-price series input"); }
		for(int i=0;i<periods-1;i++){
			williams.add(null);
		}
		for(int i=periods-1;i<series.size();i++){
			double hh=(Double)series.get(i).getHigh(), ll=(Double) series.get(i).getLow();
			for(int j=i-periods+1;j<=i;j++){
				if((Double)series.get(j).getLow()<ll){ ll=(Double) series.get(j).getLow(); }
				if((Double)series.get(j).getHigh()>hh){ hh=(Double) series.get(j).getHigh(); }
			}
			williams.add(((hh-(Double)series.get(i).getClose())/(hh-ll))*-100);
		}
		return new PlainIndicator(williams);
	}
	
	@SuppressWarnings("unchecked")
	public static PlainIndicator BollingerBandSet(boolean upper, Indicator sma, Indicator prices, int periods, int deviations){
		Vector<Double> bb=new Vector<Double>(), tSma=sma.getValues(), tPrices=prices.getValues();
		for(int i=0; i<periods; i++){ bb.add(null); }
		for(int i=periods;i<tPrices.size();i++){
			if(tSma.get(i)!=null){ 
				Double stdDev=getStdDev(periods, tSma.get(i), tPrices, i); 
				if(upper){ bb.add(tSma.get(i)+deviations*stdDev); }
				else{ bb.add(tSma.get(i)-deviations*stdDev); }
			} else { bb.add(null); }
		}
		return new PlainIndicator(bb);
	}
	
	public static Double getStdDev(int periods, Double sma, Vector<Double> dataSet, int offset){
		if(dataSet.size()<periods){ return null; }
		Double diff, variance=0.0;
		for(int i=offset;i>offset-periods;i--){
			if(dataSet.get(i)!=null){ 
				diff=dataSet.get(i)-sma;
				variance+=diff*diff;
			}
		}
		return Math.sqrt(variance/periods);
	}
	
	@SuppressWarnings("unchecked")
	public static PlainIndicator stochasticSet(Indicator highLow, Indicator series, int periods){
		Vector<Double> stochastics=new Vector<Double>(), tSeries=series.getValues();
		Vector<OHLCDataItem> tHL=highLow.getValues();
		for(int j=0;j<periods-1;j++){ stochastics.add(null); }
		for(int j=periods-1;j<tSeries.size();j++){
			double highestHigh=(Double) tHL.get(j).getHigh(), lowestLow=(Double) tHL.get(j).getLow();
			for(int i=j-periods+1;i<=j;i++){
				if((Double)tHL.get(i).getLow()<lowestLow){ lowestLow=(Double) tHL.get(i).getLow(); }
				if((Double)tHL.get(i).getHigh()>highestHigh){ highestHigh=(Double) tHL.get(i).getHigh(); }
			}
			stochastics.add((tSeries.get(j)-lowestLow)/(highestHigh-lowestLow)*100);
		}
		return new PlainIndicator(stochastics);
	}
	
	@SuppressWarnings("unchecked")
	public static PlainIndicator MACDHistSet(PlainIndicator EMA1, PlainIndicator EMA2, int signal){
		Vector<Double> MACDHist=new Vector<Double>();
		Vector<Double> MACD=MACDSet(EMA1,EMA2).getValues();
		//Data.getLogger().fine("MACDHist: Got MACD values: "+MACD.size());
		Vector<Double> MACDSignal=Indicators.EMASet(new PlainIndicator(MACD), signal).getValues();
		//Data.getLogger().fine("MACDHist: Getting difference, input sizes: "+MACD.size()+", "+MACDSignal.size());
		Data.differenceSet(MACD, MACDSignal, MACDHist);
		return new PlainIndicator(MACDHist);
	}
	
	@SuppressWarnings({ "unchecked" })
	public static PlainIndicator getIndicator(String key, Ticker ticker){
		String[] parms = key.split(",");
		PlainIndicator returnInd = null, seriesInd=null;
		//Data.getLogger().fine("Resolving "+key);
		if(parms[0].compareTo("open")==0){ return new PlainIndicator(ticker.getOpens()); }
		if(parms[0].compareTo("close")==0){ return new PlainIndicator(ticker.getCloses()); }
		if(parms[0].compareTo("high")==0){ return new PlainIndicator(ticker.getHighs()); }
		if(parms[0].compareTo("low")==0){ return new PlainIndicator(ticker.getLows()); }
		if(parms[0].compareTo("prices")==0){ return new PlainIndicator(ticker.getPrices()); }
		if(parms[0].compareTo("wc")==0){ return new PlainIndicator(Data.weightedClose(ticker.getPrices())); }
		if(parms[0].compareTo("level")==0){ return new PlainIndicator(Data.generateLevel(ticker.getOpens().size(), new Double(parms[1]))); }
		if(parms[0].compareTo("ema")==0){
			parms = key.split(",",3);
			int len=new Integer(parms[1]);
			returnInd=Indicators.EMASet(getIndicator(parms[2], ticker), len); 
		}
		if(parms[0].compareTo("sma")==0){
			parms = key.split(",",3);
			int len=new Integer(parms[1]); 
			returnInd=Indicators.SMASet(getIndicator(parms[2], ticker), len);
		}
		if(parms[0].compareTo("rsi")==0){
			parms = key.split(",",3);
			int len=new Integer(parms[1]); 
			returnInd=Indicators.rsiSet(getIndicator(parms[2], ticker), len);
		}
		if(parms[0].compareTo("wil")==0){ returnInd=Indicators.WilliamsSet(ticker.getPrices(), new Integer(parms[1])); }
		if(parms[0].compareTo("stoch")==0){ 
			parms=key.split(",",3);
			seriesInd=getIndicator(parms[2], ticker);
			returnInd=Indicators.stochasticSet(seriesInd, seriesInd, new Integer(parms[1])); 
		}
		if(parms[0].compareTo("macd")==0){ returnInd=Indicators.MACDSet(getIndicator(parms[1].replace(".", ","), ticker), getIndicator(parms[2].replace(".", ","), ticker)); }
		if(parms[0].compareTo("macdHist")==0){ 
			//Data.getLogger().fine("GetIndicator: Getting MACDHist from:");
			//Data.getLogger().fine("GetIndicator: "+parms[1].replace(".", ","));
			//Data.getLogger().fine("GetIndicator: "+parms[2].replace(".", ","));
			returnInd=Indicators.MACDHistSet(getIndicator(parms[1].replace(".", ","), ticker), getIndicator(parms[2].replace(".", ","), ticker), new Integer(parms[3])); }
		if(parms[0].compareTo("diff")==0){ 
			parms=key.split(",",3);
			returnInd=new PlainIndicator(Data.calculateDeltas(getIndicator(parms[2], ticker).getValues(), new Vector<Double>(), new Integer(parms[1]))); 
		}
		if(parms[0].compareTo("bbUpper")==0){
			//Data.getLogger().fine("Got "+key);
			parms=key.split(",",5);
			returnInd=Indicators.BollingerBandSet(true, getIndicator(parms[1].replace(".", ","), ticker), getIndicator(parms[2].replace(".", ","), ticker), new Integer(parms[3]), new Integer(parms[4]));
		}
		if(parms[0].compareTo("bbLower")==0){
			//Data.getLogger().fine("Got "+key);
			parms=key.split(",",5);
			returnInd=Indicators.BollingerBandSet(false, getIndicator(parms[1].replace(".", ","), ticker), getIndicator(parms[2].replace(".", ","), ticker), new Integer(parms[3]), new Integer(parms[4]));
		}
		if(returnInd==null){ System.out.println("WARNING: returning null indicator: "+key); }
		return returnInd;
	}
	
	@SuppressWarnings({ "unused", "unchecked" })
	private static XYDataset genPairDataset(String[] indicators, Ticker ticker){
		TimeSeriesCollection tsc = new TimeSeriesCollection();
		TimeSeries i1 = new TimeSeries(indicators[0]);
		TimeSeries i2 = new TimeSeries(indicators[1]);
		Vector<Double> v1=getIndicator(indicators[0], ticker).getValues();
		Vector<Double> v2=getIndicator(indicators[1], ticker).getValues();
		i1.add(new TimeSeriesDataItem(new Day(), v1.get(0)));
		tsc.addSeries(i1);
		return tsc;
	}
	
	public static CrossOverIndicator getCrossOverIndicator(String p1, String p2, Ticker ticker){
		return new CrossOverIndicator(p1, p2, ticker);
	}
	
	public static RangeIndicator getRangeIndicator(String rHigh, String rLow, String series, int mode, Ticker ticker){
		return new RangeIndicator(rHigh, rLow, series, ticker, mode);
	}
	
	@SuppressWarnings("unchecked")
	public static PlainIndicator MACDSet(PlainIndicator EMA1, PlainIndicator EMA2) {
		Vector<Double> difference=new Vector<Double>();
		//Data.getLogger().fine("MACD: Getting differences, input sizes: "+EMA1.getValues().size()+", "+EMA2.getValues().size());
		Data.differenceSet(EMA1.getValues(), EMA2.getValues(), difference);
		//Data.getLogger().fine("MACD: Got "+difference.size()+" differences");
		return new PlainIndicator(difference);
	}

	@SuppressWarnings("unchecked")
	public static PlainIndicator rsiSet(PlainIndicator prices, int periods){
		Vector<Double> gains=new Vector<Double>();
		Vector<Double> losses=new Vector<Double>();
		getGainsAndLosses(prices.getValues(), gains, losses);
		Vector<Double> gainEMA=EMASet(new PlainIndicator(gains), periods).getValues(), lossEMA=EMASet(new PlainIndicator(losses), periods).getValues();
		Vector<Double> rsi=new Vector<Double>();
		double rs;
		for(int i=0;i<periods*2;i++){
			rsi.add(null);
			//Data.getLogger().fine(i+") Added null");
		}
		for(int i=periods*2;i<gainEMA.size();i++){
			if(gainEMA.get(i)==0 && lossEMA.get(i)==0) rs=0;
			else rs=gainEMA.get(i)/lossEMA.get(i);
			rsi.add(100.0-(100.0/(1.0+rs)));
			//Data.getLogger().fine(i+") Added "+rsi.lastElement()+", gain="+gainEMA.get(i)+", loss="+lossEMA.get(i));
		}
		return new PlainIndicator(rsi);
	}
	
	public static void getGainsAndLosses(Vector<Double> set, Vector<Double> gains, Vector<Double> losses){
		gains.clear();
		losses.clear();
		gains.add(0.0);
		losses.add(0.0);
		for(int i=1;i<set.size();i++){
			if(set.get(i)>=set.get(i-1)){ gains.add(set.get(i)-set.get(i-1)); losses.add(0.0); }
			else{ losses.add(set.get(i-1)-set.get(i)); gains.add(0.0); }
			//Data.getLogger().fine("Added gain: "+gains.lastElement()+", loss: "+losses.lastElement());
		}
	}
}
